//Author: Max Rupplin
//Date: 1/10/08


#ifdef _WIN32
#include "boinc_win.h"
#else
#include "config.h"
#endif

#ifndef _WIN32
#include <cstdio>
#include <cctype>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <csignal>
#include <unistd.h>
#endif


#define BOINC_APP_GRAPHICS

#ifdef BOINC_APP_GRAPHICS
#include "graphics_api.h"
#include "graphics_lib.h"
#endif

#include "diagnostics.h"
#include "util.h"
#include "filesys.h"
#include "boinc_api.h"
#include "mfile.h"
#include "windows.h"
#include "stdio.h"

#include <matrix\gsl_matrix.h>
#include <multifit\gsl_multifit.h>
#include <test\gsl_test.h>
#include <sort\gsl_sort_vector.h>
#include <vector\gsl_vector.h>

#include <iostream>
#include <fstream>
#include <limits>

#include "lip.h"
#include "lippar.h"
#include "ddas_client.h"
#include "ddas_debug.h"

using namespace std;

const double DOUBLE_MAX_VALUE = numeric_limits<double>::max();

//START GLOBAL VARIABLES
gsl_matrix* master;
gsl_matrix* independent;
gsl_vector* dependent;

int columns_used=0;
int dependent_var_count=0;
int independent_var_count=0;
int results_to_return=0;
int row_count=0;
int total_result_count=0;

verylong starting_value=0;
verylong ending_value=0;
//END GLOBAL VARIABLES

//GET NUMBER OF BITS IN BINARY STRING W/ '1' VALUE
int ddas_get_one_columns_count(verylong* decimal_value)
{
	//START LOCAL VARIABLES
	char* binary=ddas_verylong_to_binary_char(decimal_value);
	int one_column_count=0;
	int length=strlen(binary);
	//END LOCAL VARIABLES

	for(int i=0; i<length; i++)
	{
		if(binary[i]=='1')one_column_count++;
	}

	return one_column_count;
}

//POPULATE GSL_MATRIX 'INDEPENDENT' WITH INDEPENDENT COLUMNS FROM 'MASTER' MATRIX
void ddas_populate_independent_matrix()
{
	//allocate some memory
	independent=gsl_matrix_calloc(row_count, independent_var_count);

	//copy proper columns from master to dependent
	for(int i=0; i<independent_var_count; i++)
	{
		//the current column we're copying
		gsl_vector* current_column = gsl_vector_calloc(row_count);

		//copy column from 'master' into 'current_column' at index 'i' of 'master'
		gsl_matrix_get_col(current_column, master, i);

		//std::cout<<"+\n";
		//ddas_print_gsl_vector_v(current_column, row_count);
		//std::cout<<"+\n";

		//set column in 'independent' matrix
		gsl_matrix_set_col(independent, i, current_column);	

		//free memory
		gsl_vector_free(current_column);
	}

	//INDEPENDENT MATRIX PRINTS CORRECTLY?
	//ddas_print_gsl_matrix(independent);

	//free the memory
	//gsl_vector_free(current_column);
}

//POPULATE GSL_VECTOR 'DEPENDENT' WITH DEPENDENT COLUMN FROM 'MASTER' MATRIX
void ddas_populate_dependent_vector()
{
	//allocate some memory
	dependent=gsl_vector_alloc(row_count);

	//copy last column of master matrix (independent_var_count) into 'dependent' vector
	gsl_matrix_get_col(dependent, master, independent_var_count);

	//INDEPENDENT MATRIX PRINTS CORRECTLY
	//ddas_print_gsl_vector(dependent);
}

//POPULATE GSL_MATRIX 'MASTER' [CONTAINS BOTH DEPENDENT & INDEPENDENT VARS]
void ddas_populate_master_matrix()
{
	FILE *csv_data_file;
	
	char char_buffer[2048];
	char resolved_name[512];

	//allocate some memory
	master=gsl_matrix_alloc(row_count, (dependent_var_count+independent_var_count));

	//boinc file i/o
	boinc_resolve_filename(CSV_DATA_FILE, resolved_name, strlen(resolved_name));
	csv_data_file = boinc_fopen(resolved_name,"r");

	//fail on file i/o error
	if (!csv_data_file) 
	{
        fprintf(stderr, "Couldn't find input file, resolved name %s.\n", resolved_name);
        exit(-1);
    }

	//populate the master matrix
	for(int r=0; r<row_count; r++)
	{
		fscanf(csv_data_file,"%s",char_buffer);
		//std::cout<<"row #"<<r<<" looks like: "<<char_buffer<<"\n";
		gsl_matrix_set(master, r, 0, atof(strtok(char_buffer,",")));

		for(int c=1; c<(dependent_var_count+independent_var_count); c++)
		{
			gsl_matrix_set(master, r, c, atof(strtok(NULL,",")));
		}
	}
}



//RETURNS INDICES OF SMALLEST 'results_to_return' CHI^2 VALS
gsl_vector* ddas_get_top_result_indices(gsl_vector* all_chi_squared_results)
{
	//START LOCAL VARIABLES
	gsl_vector* minimum_value_indices = gsl_vector_calloc(results_to_return);
	gsl_vector* local_copy = gsl_vector_calloc(all_chi_squared_results->size);
	//END LOCAL VARIABLES

	//CREATE 'LOCAL_COPY' OF CHI^2 RESULTS SO ALTERATIONS WON'T AFFECT ORIGINAL DATA
	 gsl_vector_memcpy(local_copy, all_chi_squared_results);

	//FIND 'results_to_return' INDICES OF SMALLEST VALS
	for(int i=0; i<results_to_return; i++)
	{
		//INDEX OF CURRENT SMALLEST VAL
		size_t min_val_index = (size_t) gsl_vector_min_index(local_copy);

		//RECORD INDEX OF I-TH SMALLEST VAL
		gsl_vector_set(minimum_value_indices, i, min_val_index);

		//SET VALUE AT I-TH INDEX TO MAXIMUM DOUBLE VALUE (REMOVE FROM FUTURE CONSIDERATION)
		gsl_vector_set(local_copy, min_val_index, DOUBLE_MAX_VALUE);
	}

	return minimum_value_indices;
}

//GET THE BEST 'RESULTS_TO_RETURN' RESULTS FROM 'ALL_RESULTS' AND RETURN IT
gsl_vector* ddas_get_top_results(gsl_vector* all_chi_squared_results)
{
	//START LOCAL VARIABLES
	gsl_vector* minimum_values = gsl_vector_calloc(results_to_return);
	gsl_vector* local_copy = gsl_vector_calloc(all_chi_squared_results->size);
	//END LOCAL VARIABLES

	//CREATE 'LOCAL_COPY' OF CHI^2 RESULTS SO ALTERATIONS WON'T AFFECT ORIGINAL DATA
	 gsl_vector_memcpy(local_copy, all_chi_squared_results);

	//SORT GSL_VECTOR 'ALL_RESULTS'
	gsl_sort_vector(local_copy);

	//COPY 'RESULTS_TO_RETURN' RESULTS FROM TO 'ALL_CHI_SQUARED_RESULTS' TO 'RETURN_VECTOR'
	for(int i=0; i<results_to_return; i++)
	{
		gsl_vector_set(minimum_values, i, gsl_vector_get(local_copy, i));
	}

	return minimum_values;
}

//OPENS PROBLEM_DEFINITION_FILE, PARSES AND SETS GLOBALS BASED ON DATA THEREIN
void ddas_read_problem_definition(char* infile)
{	
	//points to the file after boinc call
	FILE* problem_definition_file;

	//holds the file's resolved name
	char resolved_name[512];
	

	//boinc_resolve_filename(infile, resolved_name, sizeof(resolved_name));
	//strlen returns actual length whereas sizeof will always return 512
	boinc_resolve_filename(infile, resolved_name, strlen(resolved_name));
	problem_definition_file = boinc_fopen(resolved_name, "r");

	//fail on file error
	if (!problem_definition_file) 
	{
        fprintf(stderr,"Couldn't find input file, resolved name %s.\n",resolved_name);
        exit(-1);
    }
	
	//allocate some memory for the start and end variables
	char* c_start=new char[BINARY_ARRAY_SIZE];
	char* c_end  =new char[BINARY_ARRAY_SIZE];

	fscanf(problem_definition_file,"%*[^\n]%*s start=\"%s end=\"%s numberResults=\"%d %*s numberRows=\"%d %*s numberX=\"%d %*s numberY=\"%d", c_start, c_end, &results_to_return, &row_count, &independent_var_count, &dependent_var_count);

	//convert char* to verylong vars
	zstrtoz(c_start, &starting_value);
	zstrtoz(c_end, &ending_value);

	total_result_count=(atoi(c_end)-atoi(c_start))+1;

	ddas_print_global_vars();

	//close file
	fclose(problem_definition_file);

	//free alloc'd memory
	if(c_start!=NULL)free(c_start);
	if(c_end!=NULL)free(c_end);
}


//DO DDAS WORK [CALL GSL_MULTIFIT_LINEAR]
void ddas_worker(gsl_matrix* independent_cols, gsl_vector *coeffs, gsl_matrix *covariance, double *chi_squared)
{
	//ALLOCATE WORKSPACE
	gsl_multifit_linear_workspace* work = gsl_multifit_linear_alloc(row_count, columns_used);

	cout<<"covar before:"<<endl;
	ddas_print_gsl_matrix(covariance);
	cout<<"independent_cols before:"<<endl;
	ddas_print_gsl_matrix(independent_cols);
	//ddas_print_gsl_vector_v(dependent);
	//ddas_print_gsl_vector_h(coeffs);	

	//COMPUTE SOLUTION
	gsl_multifit_linear(independent_cols, dependent, coeffs, covariance, chi_squared, work);

	//ddas_print_gsl_matrix(dependent_cols);
	cout<<"covar after:"<<endl;
	ddas_print_gsl_matrix(covariance);
	cout<<"independent_cols after:"<<endl;
	ddas_print_gsl_matrix(independent_cols);
	//ddas_print_gsl_vector_v(independent);
	//ddas_print_gsl_vector_h(coeffs);

	//FREE WORKSPACE
	gsl_multifit_linear_free(work);
}

//CONVERTS A VERYLONG* TO A BINARY CHAR* THEN RETURNS IT
char* ddas_verylong_to_binary_char(verylong* dec)
{
	//local copy of the parameter value
	verylong local=0;

	//for modulo operations
	long two=2;
	long one=1;

	//copy parameter value to local copy
	zcopy(*dec, &local);

	//number of bits needed to represent the parameter in binary
	long bit_count=z2log(local);

	//binary array
	//char* binary=new char[bit_count+1];
	char* binary=new char[independent_var_count+1];

	//populate binary[]
	for(int i=independent_var_count-1; i>=0; i--)
	{
		if(zsmod(local, two)==one)
		{
			binary[i]='1';
		}
		else
		{
			binary[i]='0';
		}

		//local/=2
		z2div(local, &local);
	}

	//add null-termination
	binary[independent_var_count]='\0';

	return binary;
}



//GET & SET APPROPRIATE SUBSET OF GSL_MATRIX 'MASTER' AND RETURN
gsl_matrix* ddas_get_appropriate_independent_columns(verylong* iteration)
{
	//keep track of which column is next
	long current_index=0;

	//the matrix to be returned
	gsl_matrix* matrix = gsl_matrix_calloc(row_count, columns_used);
	
	//for copying the columns
	gsl_vector* vector = gsl_vector_calloc(row_count);

	//allocate some memory for the char*
	char* binary=ddas_verylong_to_binary_char(iteration);

	//ddas_print_gsl_matrix(dependent);

	//for each '1' column we include the binary
	for(long i=0; i<independent_var_count; i++)
	{
		if(binary[i]=='1')
		{
			//take column 'i' from 'master' matrix and stick it in gsl_vector 'vector'
			gsl_matrix_get_col(vector, independent, i);

			//ddas_print_gsl_vector_v(vector);

			//take 'vector' and stick it in column 'current_column' in gsl_matrix 'matrix'
			gsl_matrix_set_col(matrix, current_index, vector);	

			//increment next useable column
			current_index++;
		}
	}

	//for debugging
	//ddas_print_gsl_matrix(matrix);

	//free memory
	gsl_vector_free(vector);
	free(binary);

	//return matrix
	return matrix;
}

